﻿namespace EFS.Common
{
	#region Class StringEnum

	/// <summary>
	/// Helper class for working with 'extended' enums using <see cref="StringValueAttribute"/> attributes.
	/// </summary>
	public class StringEnum
	{
		#region Instance implementation

		private System.Type _enumType;
		private static System.Collections.Generic.Dictionary<System.Enum, StringValueAttribute> _stringValues =
			new System.Collections.Generic.Dictionary<System.Enum, StringValueAttribute>();

		/// <summary>
		/// Creates a new <see cref="StringEnum"/> instance.
		/// </summary>
		/// <param name="enumType">Enum type.</param>
		public StringEnum(System.Type enumType)
		{
			if (!enumType.IsEnum)
			{
				throw new System.ArgumentException(System.String.Format("Supplied type must be an Enum.  Type was {0}", enumType.ToString()));
			}

			_enumType = enumType;
		}

		/// <summary>
		/// Gets the string value associated with the given enum value.
		/// </summary>
		/// <param name="valueName">Name of the enum value.</param>
		/// <returns>String Value</returns>
		public string GetStringValue(string valueName)
		{
			System.Enum enumType;
			string stringValue = null;
			try
			{
				enumType = (System.Enum)System.Enum.Parse(_enumType, valueName, false);
				stringValue = GetStringValue(enumType);
			}
			catch (System.Exception) { }//Swallow!

			return stringValue;
		}

		/// <summary>
		/// Gets the string values associated with the enum.
		/// </summary>
		/// <returns>String value array</returns>
		public System.Array GetStringValues()
		{
			System.Collections.Generic.List<System.String> values = new System.Collections.Generic.List<System.String>();
			//Look for our string value associated with fields in this enum
			foreach (System.Reflection.FieldInfo fi in _enumType.GetFields())
			{
				//Check for our custom attribute
				StringValueAttribute[] attrs = fi.GetCustomAttributes(typeof(StringValueAttribute), false) as StringValueAttribute[];
				if (attrs.Length > 0)
				{
					values.Add(attrs[0].Value);
				}
			}

			return values.ToArray();
		}

		/// <summary>
		/// Gets the values as a 'bindable' list datasource.
		/// </summary>
		/// <returns>IList for data binding</returns>
		public System.Collections.IList GetListValues()
		{
			System.Type underlyingType = System.Enum.GetUnderlyingType(_enumType);
			System.Collections.Generic.List<System.Collections.DictionaryEntry> values = new System.Collections.Generic.List<System.Collections.DictionaryEntry>();
			//Look for our string value associated with fields in this enum
			foreach (System.Reflection.FieldInfo fi in _enumType.GetFields())
			{
				//Check for our custom attribute
				StringValueAttribute[] attrs = fi.GetCustomAttributes(typeof(StringValueAttribute), false) as StringValueAttribute[];
				if (attrs.Length > 0)
				{
					values.Add(
						new System.Collections.DictionaryEntry(
							System.Convert.ChangeType(
								System.Enum.Parse(_enumType, fi.Name, false),
								underlyingType,
								null),
							attrs[0].Value));
				}
			}

			return values;
		}

		/// <summary>
		/// Return the existence of the given string value within the enum.
		/// </summary>
		/// <param name="stringValue">String value.</param>
		/// <returns>Existence of the string value</returns>
		public bool IsStringDefined(string stringValue)
		{
			return Parse(_enumType, stringValue) != null;
		}

		/// <summary>
		/// Return the existence of the given string value within the enum.
		/// </summary>
		/// <param name="stringValue">String value.</param>
		/// <param name="ignoreCase">Denotes whether to conduct a case-insensitive match on the supplied string value</param>
		/// <returns>Existence of the string value</returns>
		public bool IsStringDefined(string stringValue, bool ignoreCase)
		{
			return Parse(_enumType, stringValue, ignoreCase) != null;
		}

		/// <summary>
		/// Gets the underlying enum type for this instance.
		/// </summary>
		/// <value></value>
		public System.Type EnumType
		{
			get { return _enumType; }
		}

		#endregion

		#region Static implementation

		/// <summary>
		/// Gets a string value for a particular enum value.
		/// </summary>
		/// <param name="value">Value.</param>
		/// <returns>String Value associated via a <see cref="StringValueAttribute"/> attribute, or null if not found.</returns>
		public static string GetStringValue(System.Enum value)
		{
			string output = null;
			System.Type type = value.GetType();

			if (_stringValues.ContainsKey(value))
				output = (_stringValues[value] as StringValueAttribute).Value;
			else
			{
				//Look for our 'StringValueAttribute' in the field's custom attributes
				System.Reflection.FieldInfo fi = type.GetField(value.ToString());
				StringValueAttribute[] attrs = fi.GetCustomAttributes(typeof(StringValueAttribute), false) as StringValueAttribute[];
				if (attrs.Length > 0)
				{
					_stringValues.Add(value, attrs[0]);
					output = attrs[0].Value;
				}

			}
			return output;

		}

		/// <summary>
		/// Parses the supplied enum and string value to find an associated enum value (case sensitive).
		/// </summary>
		/// <param name="type">Type.</param>
		/// <param name="stringValue">String value.</param>
		/// <returns>Enum value associated with the string value, or null if not found.</returns>
		public static object Parse(System.Type type, string stringValue)
		{
			return Parse(type, stringValue, false);
		}

		/// <summary>
		/// Parses the supplied enum and string value to find an associated enum value.
		/// </summary>
		/// <param name="type">Type.</param>
		/// <param name="stringValue">String value.</param>
		/// <param name="ignoreCase">Denotes whether to conduct a case-insensitive match on the supplied string value</param>
		/// <returns>Enum value associated with the string value, or null if not found.</returns>
		public static object Parse(System.Type type, string stringValue, bool ignoreCase)
		{
			object output = null;
			string enumStringValue = null;

			if (!type.IsEnum)
				throw new System.ArgumentException(System.String.Format("Supplied type must be an Enum.  Type was {0}", type.ToString()));

			//Look for our string value associated with fields in this enum
			foreach (System.Reflection.FieldInfo fi in type.GetFields())
			{
				//Check for our custom attribute
				StringValueAttribute[] attrs = fi.GetCustomAttributes(typeof(StringValueAttribute), false) as StringValueAttribute[];
				if (attrs.Length > 0)
					enumStringValue = attrs[0].Value;

				//Check for equality then select actual enum value.
				if (System.String.Compare(enumStringValue, stringValue, System.StringComparison.InvariantCultureIgnoreCase) == 0)
				{
					output = System.Enum.Parse(type, fi.Name, false);
					break;
				}
			}

			return output;
		}

		/// <summary>
		/// Return the existence of the given string value within the enum.
		/// </summary>
		/// <param name="stringValue">String value.</param>
		/// <param name="enumType">Type of enum</param>
		/// <returns>Existence of the string value</returns>
		public static bool IsStringDefined(System.Type enumType, string stringValue)
		{
			return Parse(enumType, stringValue) != null;
		}

		/// <summary>
		/// Return the existence of the given string value within the enum.
		/// </summary>
		/// <param name="stringValue">String value.</param>
		/// <param name="enumType">Type of enum</param>
		/// <param name="ignoreCase">Denotes whether to conduct a case-insensitive match on the supplied string value</param>
		/// <returns>Existence of the string value</returns>
		public static bool IsStringDefined(System.Type enumType, string stringValue, bool ignoreCase)
		{
			return Parse(enumType, stringValue, ignoreCase) != null;
		}

		#endregion
	}

	#endregion

	#region Class StringValueAttribute

	/// <summary>
	/// Simple attribute class for storing String Values
	/// </summary>
	public class StringValueAttribute : System.Attribute
	{
		/// <summary>
		/// Creates a new <see cref="StringValueAttribute"/> instance.
		/// </summary>
		/// <param name="value">Value.</param>
		public StringValueAttribute(string value)
		{
			Value = value;
		}

		/// <summary>
		/// Gets the value.
		/// </summary>
		/// <value></value>
		public string Value { get; private set; }
	}

	#endregion

}